﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Reflection;

namespace INSTA.Library
{
    public static class ReflectionHelper
    {

        /// <summary>
        /// Determines whether the specified MemberInfo is decorated with the specified attribute.
        /// </summary>
        /// <typeparam name="T">the attribute type</typeparam>
        /// <param name="target">The target.</param>
        /// <returns>
        /// true if the specified Type is decorated with the specified attribute.
        /// </returns>
        public static bool IsDecoratedWith<T>(this MemberInfo target) where T : Attribute
        {
            // Returns an array of attributes of type "attributeType"
            var attributes = target.GetCustomAttributes(typeof(T), true);

            // if there is at least one element in the array,
            // return true..
            return attributes.Length > 0;
        }

        /// <summary>
        /// Determines whether the specified MemberInfo is decorated with the specified attribute.
        /// </summary>
        /// <typeparam name="T">the attribute type</typeparam>
        /// <param name="target">The target.</param>
        /// <returns>
        /// true if the specified Type is decorated with the specified attribute.
        /// </returns>
        public static bool IsDecoratedWith<T>(this object target) where T : Attribute
        {
            return target.GetType().IsDecoratedWith<T>();
        }

        


        /// <summary>
        /// Gets the unique attribute.
        /// </summary>
        /// <typeparam name="TAttribute">The type of the attribute.</typeparam>
        /// <param name="target">The target.</param>
        /// <returns>
        ///     true if the specified  MemberInfo is decorated only once with 
        ///     the specified attribute type. 
        /// </returns>
        public static TAttribute GetUniqueAttribute<TAttribute>(this MemberInfo target) where TAttribute: Attribute
        {
            // Returns an array of attributes of type "attributeType"
            var attributes = target.GetCustomAttributes(typeof(TAttribute), true);

            return attributes[0] as TAttribute;
        }

        /// <summary>
        /// Gets the unique attribute.
        /// </summary>
        /// <typeparam name="TAttribute">The type of the attribute.</typeparam>
        /// <param name="target">The target.</param>
        /// <returns>
        ///     true if the specified  MemberInfo is decorated only once with 
        ///     the specified attribute type. 
        /// </returns>
        public static TAttribute GetUniqueAttribute<TAttribute>(this object target) where TAttribute: Attribute
         {
             return target.GetType().GetUniqueAttribute<TAttribute>();
         }



        /// <summary>
        /// Sets the property value.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="propertyValue">The property value.</param>
         public static void SetPropertyValue(this object target, 
                                             string propertyName, 
                                             object propertyValue)
         {
             // let's get the target object's Type..
             var targetType = target.GetType();

             var flags = GetStandardBindings();


             // dynamically invoke the property setter..
             targetType.InvokeMember(propertyName,                      // Nom du membre
                                     flags | BindingFlags.SetProperty,  // Flags d'accès
                                     null,                              // Binder (tjs null)
                                     target,                            // objet cible
                                     new object[] {propertyValue});     // valeur à affecter
         }


         /// <summary>
         /// Gets the property value.
         /// </summary>
         /// <param name="target">The target.</param>
         /// <param name="propertyName">Name of the property.</param>
         /// <returns>the property value</returns>
        public static object GetPropertyValue(this object target,
                                              string propertyName)
        {
            // let's get the target object's Type..
            var targetType = target.GetType();
            
            var flags = GetStandardBindings();


            // dynamically invoke the property setter..
            var result = targetType.InvokeMember(propertyName,                      // Nom du membre
                                    flags | BindingFlags.GetProperty,  // Flags d'accès
                                    null,                              // Binder (tjs null)
                                    target,                            // objet cible
                                    null);     // valeur à affecter
            return result;
        }


        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <typeparam name="T">type of the value to return</typeparam>
        /// <param name="target">The target.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns>the typed property value.</returns>
        public static T GetPropertyValue<T>(this object target,
                                            string propertyName) 
        {
            // let's get the target object's Type..
            var targetType = target.GetType();

            var flags = GetStandardBindings();


            // dynamically invoke the property setter..
            var result = targetType.InvokeMember(propertyName,         // Nom du membre
                                    flags | BindingFlags.GetProperty,  // Flags d'accès
                                    null,                              // Binder (tjs null)
                                    target,                            // objet cible
                                    null);     // valeur à affecter
            return (T)result;
        }






        /// <summary>
        /// Invokes the method.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>the value returned by the method, if any.</returns>
        public static object InvokeMethod(this object target,
                                          string methodName,
                                          params object[] parameters)
        {
            // let's get the target object's Type..
            var targetType = target.GetType();

            var flags = GetStandardBindings();


            // dynamically invoke the property setter..
            var result = targetType.InvokeMember(methodName,            // Nom du membre
                                    flags | BindingFlags.InvokeMethod,  // Flags d'accès
                                    null,                               // Binder (tjs null)
                                    target,                             // objet cible
                                    parameters);                        // valeur(s) à affecter
            return result;
        }


        /// <summary>
        /// Invokes the method.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>the value returned by the method, if any.</returns>
        public static T InvokeMethod<T>(this object target,
                                        string methodName,
                                        params object[] parameters) 
        {
            // let's get the target object's Type..
            var targetType = target.GetType();

            var flags = GetStandardBindings();


            // dynamically invoke the property setter..
            var result = targetType.InvokeMember(methodName,            // Nom du membre
                                    flags | BindingFlags.InvokeMethod,  // Flags d'accès
                                    null,                               // Binder (tjs null)
                                    target,                             // objet cible
                                    parameters);                        // valeur(s) à affecter
            return (T)result;
        }


        /// <summary>
         /// Gets the standard bindings.
         /// </summary>
         /// <returns>the standard bindings.</returns>
        private static BindingFlags GetStandardBindings()
        {
            BindingFlags flags = BindingFlags.Public |
                                 BindingFlags.NonPublic |
                                 BindingFlags.Static |
                                 BindingFlags.Instance;
            return flags;
        }




        /// <summary>
        /// Returns true if the specified <see cref="Type"/> implements
        /// the specified interface.
        /// </summary>
        /// <typeparam name="TInterfaceType">The interface type to check for.</typeparam>
        /// <param name="type">The class to check.</param>
        /// <returns>true if the class to check implements the specified interface.</returns>
        public static bool ImplementsInterface<TInterfaceType>(this Type type)
        {
            var result = false;

            var implementedInterfaces = (from x in type.GetInterfaces()
                                         where x == typeof(TInterfaceType)
                                         select x).Count();

            result = (implementedInterfaces > 0);

            return result;
        }

        /// <summary>
        /// Returns true if the specified object implements
        /// the specified interface.
        /// </summary>
        /// <typeparam name="TInterfaceType">The interface type to check for.</typeparam>
        /// <param name="target">The object to check.</param>
        /// <returns>true if the object to check implements the specified interface.</returns>
        public static bool ImplementsInterface<TInterfaceType>(this object target)
        {
            return ImplementsInterface<TInterfaceType>(target.GetType());
        }


        /// <summary>
        /// Determines whether the specified Type is derived 
        /// from the type of T.
        /// </summary>
        /// <typeparam name="T">The Type</typeparam>
        /// <param name="typeToTest">The type to test.</param>
        /// <returns>
        /// true if the type to test is derived from T,
        /// else, false.
        /// </returns>        
        public static bool IsDerivedFrom<T>(this Type typeToTest)
        {
            bool result = false;

            if (!typeToTest.IsInterface)
            {
                while (typeToTest.BaseType != typeof(object))
                {
                    if (typeToTest.BaseType == typeof(T))
                    {
                        result = true;
                        break;
                    }
                    typeToTest = typeToTest.BaseType;
                }
            }
            return result;
        }


        /// <summary>
        /// Determines whether the specified Type is derived 
        /// from the type of T.
        /// </summary>
        /// <typeparam name="T">The Type</typeparam>
        /// <param name="typeToTest">The type to test.</param>
        /// <returns>
        /// true if the type to test is derived from T,
        /// else, false.
        /// </returns>        
        public static bool IsDerivedFrom<T>(this object typeToTest)
        {   
            return IsDerivedFrom<T>(typeToTest.GetType());
        }





























    }
}
